// The gosymbols command prints type information for package-level symbols.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"os"
	"strings"
)

const usage = `Usage: goref <package> <type>`

func main() {
	pkg := flag.String("package", "net/http", "")
	types := flag.String("type", "PostForm", "")
	flag.Parse()
	if err := exec(*pkg, *types); err != nil {
		fmt.Fprintf(os.Stderr, "go-symbols: %s\n", err)
		os.Exit(1)
	}
}

type symbol struct {
	Name      string `json:"name"`
	Kind      string `json:"kind"`
	Package   string `json:"package"`
	Path      string `json:"path"`
	Line      int    `json:"line"`
	Character int    `json:"character"`
}

var syms = make([]symbol, 0)

type visitor struct {
	pkg   *ast.Package
	fset  *token.FileSet
	query string
	syms  []symbol
}

func (v *visitor) Visit(node ast.Node) bool {
	descend := true

	var ident *ast.Ident
	var kind string
	switch t := node.(type) {
	case *ast.FuncDecl:
		kind = "func"
		ident = t.Name
		descend = false

	case *ast.TypeSpec:
		kind = "type"
		ident = t.Name
		descend = false
	}
	if ident != nil && strings.Contains(ident.Name, v.query) {
		f := v.fset.File(ident.Pos())
		v.syms = append(v.syms, symbol{
			Package: v.pkg.Name,
			Path:    f.Name(),
			Name:    ident.Name,
			Kind:    kind,
			Line:    f.Line(ident.Pos()) - 1,
		})
	}

	return descend
}

func exec(pkg, types string) error {
	p, err := build.Default.Import(pkg, "", 0)
	fset := token.NewFileSet()
	v := &visitor{
		fset:  fset,
		query: types,
	}
	parsed, _ := parser.ParseDir(fset, p.Dir, nil, 0)
	for _, astpkg := range parsed {
		v.pkg = astpkg
		for _, f := range astpkg.Files {
			ast.Inspect(f, v.Visit)
		}
	}
	buf, err := json.Marshal(v.syms)
	if err != nil {
		panic(err)
	}
	os.Stdout.Write(buf)
	return nil
}
